package com.benshou.bcss.common.quartz.bean;

import com.benshou.bcss.common.j17common.Requires;
import com.benshou.bcss.common.j17common.annotation.NotNull;
import com.benshou.bcss.common.quartz.JobKeyRegister;
import com.benshou.bcss.common.quartz.QuartzJobRunner;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.quartz.*;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Date;
import java.util.TimeZone;
import java.util.stream.Collectors;

/**FIXME 利用 org.springframework.core.env.PropertyResolver 适配 ${property}
 * 基于 Spring Bean 的 Quartz 定时任务注册器
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
@Component
@RequiredArgsConstructor
public class BeanQuartzJobRegister implements BeanPostProcessor {

    /**
     * Key: Bean 在 Spring 上下文中的名称
     */
    public static final String KEY_BEAN_NAME = "beanName";

    /**
     * Key: 定时任务对应方法的名称
     */
    public static final String KEY_METHOD_NAME = "methodName";

    /**
     * Key: 定时任务对应方法的参数的全限定名称列表。名称以“,”连接。
     */
    public static final String KEY_PARAMETER_TYPE_NAMES = "parameterTypeNames";

    /**
     * JobKey 注册器
     */
    private final JobKeyRegister jobKeyRegister;

    /**
     * 任务调度实例
     */
    private final Scheduler scheduler;

    @Override
    public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
        // 对于每个 Bean ，初始化后检查如有被 @BeanQuartz 注解的方法，加入到 Quartz 定时任务调度中
        var cls = AopUtils.getTargetClass(bean);
        ReflectionUtils.doWithMethods(cls, method -> {
            var beanQuartz = AnnotationUtils.getAnnotation(method, BeanQuartz.class);
            if (beanQuartz != null) {
                registerJob(cls, method, beanQuartz, beanName);
            }});

        return bean;
    }

    /**
     * 注册定时任务
     *
     * @param cls 定时任务对应的方法所在的 Class
     * @param method 定时任务对应的方法
     * @param beanQuartz 定时任务对应的方法上的 BeanQuartz 注解
     * @param beanName 定时任务对应的方法所在的 Bean 在 Spring 上下文中的名称
     */
    @SneakyThrows(SchedulerException.class)
    private void registerJob(
            @NotNull Class<?> cls,
            @NotNull Method method,
            @NotNull BeanQuartz beanQuartz,
            @NotNull String beanName) {
        // 构建 JobDetail
        var group = Requires.notBlank(
                beanQuartz.group(),
                () -> new IllegalArgumentException("Quartz job group is blank"));

        var jobKey = new JobKey(generateJobName(cls, method, beanQuartz, beanName), group);
        this.jobKeyRegister.register(jobKey);

        if (this.scheduler.checkExists(jobKey)) {
            // FIXME 不能直接返回，应当检查参数，如有修改，进行覆盖
            return;
        }

        var jobDataMap = new JobDataMap();
        jobDataMap.put(KEY_BEAN_NAME, beanName);
        jobDataMap.put(KEY_METHOD_NAME, method.getName());
        var parameterTypes = method.getParameterTypes();
        if (parameterTypes.length > 0) {
            jobDataMap.put(
                    KEY_PARAMETER_TYPE_NAMES,
                    Arrays.stream(parameterTypes)
                            .map(Class::getName)
                            .collect(Collectors.joining(",")));
        }

        var jobDetail = JobBuilder.newJob(QuartzJobRunner.class)
                .withIdentity(jobKey)
                .withDescription(beanQuartz.description())
                .usingJobData(jobDataMap)
                .storeDurably()
                .build();

        // 构建触发器
        var trigger = buildTrigger(jobKey, beanQuartz);

        // 注册定时任务
        this.scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 构建触发器
     *
     * @param jobKey JobKey
     * @param beanQuartz 注解内容
     * @return 构建好的触发器
     */
    private Trigger buildTrigger(@NotNull JobKey jobKey, @NotNull BeanQuartz beanQuartz) {
        // 触发器构建
        var triggerKey = new TriggerKey("Trigger_" + jobKey.getName(), jobKey.getGroup());
        var triggerBuilder = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withDescription(beanQuartz.description())
                .withPriority(beanQuartz.priority());

        var startDelay = beanQuartz.startDelay();
        triggerBuilder = startDelay > 0 ?
                triggerBuilder.startAt(new Date(System.currentTimeMillis() + startDelay)) :
                triggerBuilder.startNow();

        if (beanQuartz.endTimestamp() > 0) {
            triggerBuilder = triggerBuilder.endAt(new Date(beanQuartz.endTimestamp()));
        }

        // 根据触发器类型创建对应的调度器
        var scheduleBuilder = (ScheduleBuilder<?>) switch (beanQuartz.triggerType()) {
            // 简单触发器
            case SIMPLE -> {
                var builder = SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMilliseconds(beanQuartz.repeatInterval());

                var repeatCount = beanQuartz.repeatCount();
                builder = repeatCount < 0 ?
                        builder.repeatForever() :
                        builder.withRepeatCount(repeatCount);

                builder = switch (beanQuartz.misfirePolicy()) {
                    case FIRE -> builder.withMisfireHandlingInstructionFireNow();
                    case IGNORE -> builder.withMisfireHandlingInstructionIgnoreMisfires();
                    default -> builder;};

                yield builder;}
            // Cron 表达式触发器
            case CRON -> {
                var builder = CronScheduleBuilder.cronSchedule(beanQuartz.cron());

                var timezone = beanQuartz.timezone();
                if (!timezone.isBlank()) {
                    builder = builder.inTimeZone(TimeZone.getTimeZone(ZoneId.of(timezone)));
                }

                builder = switch (beanQuartz.misfirePolicy()) {
                    case FIRE -> builder.withMisfireHandlingInstructionFireAndProceed();
                    case IGNORE -> builder.withMisfireHandlingInstructionIgnoreMisfires();
                    case DO_NOTHING -> builder.withMisfireHandlingInstructionDoNothing();
                    default -> builder;};

                yield builder;}
            default ->
                    throw new UnsupportedOperationException("Unsupported trigger type: " + beanQuartz.triggerType());};

        // 生成触发器
        return triggerBuilder.withSchedule(scheduleBuilder).build();
    }

    /**
     * 生成定时任务调度的任务名称
     *
     * @param cls 定时任务对应的方法所在的 Class
     * @param method 定时任务对应的方法
     * @param beanQuartz 定时任务对应的方法上的 BeanQuartz 注解
     * @param beanName 定时任务对应的方法所在的 Bean 在 Spring 上下文中的名称
     * @return 任务名称
     */
    private static String generateJobName(
            @NotNull Class<?> cls,
            @NotNull Method method,
            @NotNull BeanQuartz beanQuartz,
            @NotNull String beanName) {
        //  若设定了名称，直接返回设定的名称
        var name = beanQuartz.name();
        if (!name.isBlank()) {
            return name;
        }

        // 构建 beanName.methodName(paramTypeName1,paramTypeName2,...) 格式的方法名
        var nameBuilder = new StringBuilder()
                .append(beanName)
                .append(".")
                .append(method.getName())
                .append("(");
        var parameterTypes = method.getParameterTypes();
        if (parameterTypes.length > 0) {
            for (var parameterType : parameterTypes) {
                nameBuilder.append(parameterType.getSimpleName()).append(",");
            }
            nameBuilder.deleteCharAt(nameBuilder.length() - 1);
        }
        nameBuilder.append(")");

        return nameBuilder.toString();
    }
}
